#pragma once
#include <Math/Vector2.hpp>
#include <Math/Array2.hpp>
#include <Utility/IOInterface.hpp>
#include "ImageDefines.hpp"

//Image cooridnate is row(bottom to top), col(left to right)
//always use row and column
//therefore x,y must be translated

//make it this way is to coorperate with opengl
//so you do not need to flip when using textures or drawpi

namespace zzz{
template<typename T>
class Image : public Array<2,T>, public IOData {
  using Array<2,T>::sizes;
public:
  Image()
  :orir_(0),oric_(0) {
  }
  Image(const Image<T> &image)
  :orir_(image.orir_),oric_(image.oric_) {
    *this=image;
  }
  Image(int nrow, int ncol)
  :Array<2,T>(nrow, ncol), orir_(0),oric_(0) {
  }
  explicit Image(const string &filename)
  :orir_(0),oric_(0) {
  LoadFile(filename);
  }
  explicit Image(const Vector2ui size)
  :Array<2,T>(size),orir_(0),oric_(0) {
  }

  using Array<2,T>::SetSize;
  zuint Rows() const {
    return sizes[0];
  }
  zuint Cols() const {
    return sizes[1];
  }
  bool LoadFile(const string &filename);
  bool SaveFile(const string &filename);
  
  //half width, half height
  void HalfImageTo(Image<T> &image) const;
  void HalfImage() {
    Image<T> img; 
    HalfImageTo(img); 
    *this=img;
  }

  //double width, double height, no interpolation
  void DoubleImageTo(Image<T> &image) const;
  void DoubleImage() {
    Image<T> img;
    DoubleImageTo(img); 
    *this=img;
  }

  //enlarge or shrink image
  void ResizeTo(Image<T> &image) const;
  void Resize(int nrow, int ncol) {
    Image<T> img(nrow,ncol); 
    if (sizes[0]!=0 && sizes[1]!=0) 
      ResizeTo(img); 
    *this=img;
  }

  //access
  T & At(int r, int c) {
    return Array<2,T>::At(r+orir_, c+oric_);
  }
  const T & At(int r, int c) const {
    return Array<2,T>::At(r+orir_, c+oric_);
  }
  T & operator()(int r, int c) {
    return At(r, c);
  }
  const T & operator()(int r, int c) const {
    return At(r, c);
  }

  //toindex
  zuint ToIndex(const VectorBase<2,int> &pos) const {
    return Vector2ui(pos[0]+orir_,pos[1]+oric_).Dot(Array<2,T>::subsizes);
  }
  VectorBase<2,int> ToIndex(zuint idx) const {
    Vector<2,int> pos(Array<2,T>::ToIndex(idx));
    pos[0]-=orir_;
    pos[1]-=oric_;
    return pos;
  }

  //interpolate
  template<typename T1>
  T Interpolate(const Vector<2,T1> &rc) const {
    return Interpolate(rc[0], rc[1]);
  }
  template<typename T1>
  T Interpolate(T1 r, T1 c) const {
    double row(r), col(c);
    // do the interpolation
    int r0=Clamp<int>(-orir_,floor(row),int(Rows())-1-orir_);
    int r1=Clamp<int>(-orir_,ceil(row),int(Rows())-1-orir_);
    int c0=Clamp<int>(-oric_,floor(col),int(Cols())-1-oric_);
    int c1=Clamp<int>(-oric_,ceil(col),int(Cols())-1-oric_);
    const double fractR = row - r0;
    const double fractC = col - c0;
    const T syx = At(r0,c0);
    const T syx1 = At(r0,c1);
    const T sy1x = At(r1,c0);
    const T sy1x1 = At(r1,c1);
    // normal interpolation within range
    const T tmp1 = syx  + (syx1-syx)*fractC;
    const T tmp2 = sy1x + (sy1x1-sy1x)*fractC;
    return (tmp1 + (tmp2-tmp1)*fractR);
  }


  using Array<2,T>::operator[];
  
  //inside image
  template<typename T1>
  bool IsInside(const Vector<2,T1> &pos) const {
    if (Within<T1>(0,pos[0]+orir_,Rows()-EPSILON) && Within<T1>(0,pos[1]+oric_,Cols()-EPSILON)) return true;
    else return false;
  }

  //upleft is zero, right is x, down is y
  template<typename T1>
  Vector<2,T1> ConvertImageCoordFromXY(T1 x, T1 y) const {
    return Vector<2,T1>(Rows()-1-y,x);
  }

  //downleft is zero, right is x, up is y
  template<typename T1>
  Vector<2,T1> ConvertImageCoordFromX_Y(T1 x, T1 y) const {
    return Vector<2,T1>(y,x);
  }

public:
  int orir_, oric_;
public:
  static const int Channels_,Format_,Type_;
};

template<typename T>
void zzz::Image<T>::ResizeTo(Image<T> &dest) const {
  zuint r,c;
  double fr,fc;
  const double dc = static_cast<double>(Cols()-1)/(dest.Cols()-1);
  const double dr = static_cast<double>(Rows()-1)/(dest.Rows()-1);
  for (fr=0.0,r=0; r<dest.Rows(); ++r,fr+=dr) for (fc=0.0,c=0; c<dest.Cols(); ++c,fc+=dc) 
    dest.At(r,c) = Interpolate(fr,fc);
}

template<typename T>
void zzz::Image<T>::DoubleImageTo(Image<T> &image) const {
  int row=Rows()*2;
  int column=Cols()*2;
  image.SetSize(row,column);
  for (int i=0; i<row; i++) for (int j=0; j<column; j++)
    image.At(i,j)=At(i/2,j/2);
}
template<typename T>
void zzz::Image<T>::HalfImageTo(Image<T> &image) const {
  int row=Rows()/2;
  int column=Cols()/2;
  image.SetSize(row,column);
  for (int i=0; i<row; i++) for (int j=0; j<column; j++)
    image.At(i,j)=At(i*2,j*2);
}
typedef Image<float> Imagef;
typedef Image<zuchar> Imageuc;
typedef Image<Vector3f> Image3f;
typedef Image<Vector3uc> Image3uc;
typedef Image<Vector4f> Image4f;
typedef Image<Vector4uc> Image4uc;

// IOObject
template<typename T>
class IOObject<Image<T> >
{
public:
  static void WriteFileB(FILE *fp, const Image<T> &src) {
    IOObject<ArrayBase<2,T> >::WriteFileB(fp, src);
    IOObject<int>::WriteFileB(fp, src.orir_);
    IOObject<int>::WriteFileB(fp, src.oric_);
  }
  static void ReadFileB(FILE *fp, Image<T>& dst) {
    IOObject<ArrayBase<2,T> >::ReadFileB(fp, dst);
    IOObject<int>::ReadFileB(fp, dst.orir_);
    IOObject<int>::ReadFileB(fp, dst.oric_);
  }
  static void WriteFileR(RecordFile &fp, const zint32 label, const Image<T>& src) {
    IOObject<ArrayBase<2,T> >::WriteFileR(fp, label, src);
    IOObject<int>::WriteFileR(fp, src.orir_);
    IOObject<int>::WriteFileR(fp, src.oric_);
  }
  static void ReadFileR(RecordFile &fp, const zint32 label, Image<T>& dst) {
    IOObject<ArrayBase<2,T> >::ReadFileR(fp, label, dst);
    IOObject<int>::ReadFileR(fp, dst.orir_);
    IOObject<int>::ReadFileR(fp, dst.oric_);
  }
};

}
